using System;
using System.Collections.Generic;
using System.Text;
using System.Runtime.InteropServices;
using System.IO;
using System.Drawing;
using System.Drawing.Drawing2D;
using System.Windows.Forms;

using DirectShowLib;

namespace Pg.BioMedics.RatObserver.Media
{
    /// <summary>
    /// Abstract video file representation class
    /// </summary>
    public sealed class Video
    {
        #region Fields
        
        private readonly string _FileName;

        #endregion

        #region Consturctors

        /// <summary>
        /// Initializes instance of Video object for specified video file
        /// </summary>
        /// <param name="videoFile">video file</param>
        public Video(string videoFile)
        {
            // input params validation
            if (!File.Exists(videoFile))
                throw new FileNotFoundException(String.Format("Video object can't be initialized. Specified file {0} doesn't exist", videoFile), videoFile);

            _FileName = videoFile;
        }

        #endregion

        #region Properties

        public string FileName
        {
            get
            {
                return _FileName;
            }
        }

        #endregion

        #region Methods

        /// <summary>
        /// Initializes frame extraction. Creates DirectShow graph 
        /// with frame grabber filter and creates instance of FrameBuffer
        /// class
        /// </summary>
        /// <param name="capacity">max number of frames in frame buffer</param>
        /// <param name="type">bitmap type (color bit depth)</param>
        /// <returns>frame buffer object</returns>
        public FrameBuffer Extract(int capacity, BitmapType type)
        {
            // input params validation
            if (capacity <= 0)
                throw new ArgumentException(String.Format("Can't extract frames, frame buffer capacity {0} is invalid. Value should be greater than 0", capacity),
                                            "capacity");
            else if (!Enum.IsDefined(typeof(BitmapType), type))
                throw new ArgumentException(String.Format("Can't extract frames, bitmap type {0} is invalid", type));

            try
            {
                // Filter graph
                IGraphBuilder graphBuilder = new FilterGraph() as IGraphBuilder;


                // create source filter
                IBaseFilter sourceFilter = null;
                int hr = graphBuilder.AddSourceFilter(_FileName, "ExtractorSourceFilter", out sourceFilter);
                DsError.ThrowExceptionForHR(hr);


                //create sample grabber
                ISampleGrabber sampleGrabber = new SampleGrabber() as ISampleGrabber;
                IBaseFilter sampleGrabberFilter = sampleGrabber as IBaseFilter;
                SampleGrabberCB grabber = new SampleGrabberCB(type);

                AMMediaType media = new AMMediaType();
                media.majorType = MediaType.Video;
                media.subType = (type == BitmapType.RGB8) ? MediaSubType.RGB8 : MediaSubType.RGB24;
                media.formatType = FormatType.VideoInfo;

                hr = sampleGrabber.SetMediaType(media);
                DsError.ThrowExceptionForHR(hr);

                DsUtils.FreeAMMediaType(media);
                media = null;

                hr = sampleGrabber.SetCallback(grabber, 1);
                DsError.ThrowExceptionForHR(hr);

                hr = graphBuilder.AddFilter(sampleGrabberFilter, "SampleGrabberFilter");
                DsError.ThrowExceptionForHR(hr);


                // create null renderer filter
                IBaseFilter rendererFilter = new NullRenderer() as IBaseFilter;

                hr = graphBuilder.AddFilter(rendererFilter, "NullRendererFilter");
                DsError.ThrowExceptionForHR(hr);


                // connect graph
                IPin sourceOutPin = DsFindPin.ByDirection(sourceFilter, PinDirection.Output, 0);
                IPin grabberInPin = DsFindPin.ByDirection(sampleGrabberFilter, PinDirection.Input, 0);
                IPin grabberOutPin = DsFindPin.ByDirection(sampleGrabberFilter, PinDirection.Output, 0);
                IPin rendererInPin = DsFindPin.ByDirection(rendererFilter, PinDirection.Input, 0);

                hr = graphBuilder.Connect(sourceOutPin, grabberInPin);
                DsError.ThrowExceptionForHR(hr);

                hr = graphBuilder.Connect(grabberOutPin, rendererInPin);
                DsError.ThrowExceptionForHR(hr);


                // get grabbed frame size
                media = new AMMediaType();
                hr = sampleGrabber.GetConnectedMediaType(media);
                DsError.ThrowExceptionForHR(hr);

                VideoInfoHeader videoInfoHeader = (VideoInfoHeader)Marshal.PtrToStructure(media.formatPtr, typeof(VideoInfoHeader));
                grabber.FrameWidth = videoInfoHeader.BmiHeader.Width;
                grabber.FrameHeight = videoInfoHeader.BmiHeader.Height;
                grabber.FrameStride = grabber.FrameWidth * (videoInfoHeader.BmiHeader.BitCount / 8);

                DsUtils.FreeAMMediaType(media);
                media = null;


                // create frame buffer
                return new FrameBuffer(graphBuilder, grabber, capacity);
            }
            catch (Exception ex)
            {
                throw new MediaException("Can't extract frames", ex);
            }
        }

        /// <summary>
        /// Makes DirectShow graph initialization. Prepares PlayControl utility object
        /// utilized for playbeck control
        /// </summary>
        /// <param name="coltrol">control on which wideo will be rendered</param>
        /// <returns>play control object</returns>
        public PlayControl Play(Control control)
        {
            try
            {
                // graph items
                int hr;
                IGraphBuilder graphBuilder = new FilterGraph() as IGraphBuilder;


                // add source filter
                IBaseFilter sourceFilter = null;
                hr = graphBuilder.AddSourceFilter(_FileName, "VideoSource", out sourceFilter);
                DsError.ThrowExceptionForHR(hr);


                // start graph
                IMediaControl mediaControl = graphBuilder as IMediaControl;
                mediaControl.RenderFile(_FileName);


                // set up output wiindo
                IVideoWindow videoWindow = graphBuilder as IVideoWindow;

                hr = videoWindow.put_Owner(control.Handle);
                DsError.ThrowExceptionForHR(hr);

                hr = videoWindow.put_WindowStyle((WindowStyle.Child | WindowStyle.ClipChildren | WindowStyle.ClipSiblings));
                DsError.ThrowExceptionForHR(hr);

                hr = videoWindow.put_Visible(OABool.True);
                DsError.ThrowExceptionForHR(hr);

                Rectangle rc = control.ClientRectangle;
                hr = videoWindow.SetWindowPosition(0, 0, rc.Right, rc.Bottom);
                DsError.ThrowExceptionForHR(hr);


                // create play control util object
                return new PlayControl(graphBuilder);
            }
            catch (Exception ex)
            {
                throw new MediaException("Can't play video", ex);
            }
        }

        public void Convert(string targetFile)
        {
            throw new NotImplementedException();
        }

        #endregion
    }
}
